package sk.upjs.ics.fca.bonds.efficient;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import sk.upjs.ics.fca.concepts.ClosureType;
import sk.upjs.ics.fca.concepts.Concept;
import sk.upjs.ics.fca.concepts.Context;
import sk.upjs.ics.fca.concepts.Logic;

/**
 * This class takes care of generating bonds between 2 contexts and makes use of
 * our algorithm. This algorithm was inspired by algorithm which returns all
 * concepts of given context from R. Belohlavek and his team. Our algorithm is
 * the extension of this algorithm.
 * 
 * @author Patrik Mihalcin
 */
public class BondsFactory {

	private Context c1 = null;
	private Context c2 = null;
	private Map<List<List<Double>>, Map<Character, Set<List<List<Double>>>>> latticeOfBonds = new HashMap<List<List<Double>>, Map<Character, Set<List<List<Double>>>>>();

	/**
	 * Creates connection between given contexts and calls method lattice in
	 * order to generate lattices of concepts of contexts
	 * 
	 * @param c1
	 *            first context
	 * @param c2
	 *            second context
	 */
	public BondsFactory(Context c1, Context c2) {
		this.c1 = c1;
		this.c2 = c2;

		c1.lattice(ClosureType.UP_DOWN);
		c2.lattice(ClosureType.DOWN_UP);
	}

	/**
	 * Returns the set of all bonds between given contexts
	 * 
	 * @return set of bonds
	 */
	public Set<List<List<Double>>> getBonds() {
		return latticeOfBonds.keySet();
	}

	/**
	 * Returns the lattice of all bonds between given contexts and the
	 * information about upper and lower neighbors are included either
	 * 
	 * @return zvaz vsetkych bondov
	 */
	public Map<List<List<Double>>, Map<Character, Set<List<List<Double>>>>> getLatticeOfBonds() {
		return latticeOfBonds;
	}

	/**
	 * Starts generating of lattice of all bonds between given contexts The
	 * scenario is: <br />
	 * 1. build multifunction which consists of lowest intents of second context <br />
	 * 2. do left multifunction closure <br />
	 * 3. recursively generate all upper neighbors
	 */
	public void generateLatticeOfBonds() {

		// find out lowest intent of context C2
		Map<List<Double>, List<List<Double>>> lowerNeighborsC2 = c2
				.getLowerNeighbors();

		List<Double> lowestIntent = new ArrayList<Double>();

		for (List<Double> intent : c2.getIntents()) {
			if (lowerNeighborsC2.get(intent).isEmpty()) {
				lowestIntent = intent;
				break;
			}
		}

		// build multifunction beta using lowest intent from context C2
		List<List<Double>> beta = new ArrayList<List<Double>>();
		for (int i = 0; i < c1.getObjectsCount(); i++) {
			beta.add(lowestIntent);
		}

		beta = leftMultiFunctionClosure(beta);

		latticeOfBonds.put(beta,
				new HashMap<Character, Set<List<List<Double>>>>());
		latticeOfBonds.get(beta).put('l', new HashSet<List<List<Double>>>());
		latticeOfBonds.get(beta).put('u', new HashSet<List<List<Double>>>());

		generateFrom(beta);
	}

	/**
	 * This method assigns all upper neighbors to input bond (or multifunction)
	 * and create between them upper/lower neighbor connections. Recursively
	 * calls itself for all upper neighbors of input bond.
	 * 
	 * @param multiFunction
	 *            bond
	 */
	private void generateFrom(List<List<Double>> multiFunction) {
		Set<List<List<Double>>> upperNeighbors = upperNeighbors(multiFunction);
		latticeOfBonds.get(multiFunction).put('u', upperNeighbors);
		// find out newly found upper neighbors = get rid of those which have
		// been found previously
		Set<List<List<Double>>> bigN = new HashSet<List<List<Double>>>();

		for (List<List<Double>> upperNeighbor : latticeOfBonds.get(
				multiFunction).get('u')) {
			if (!latticeOfBonds.keySet().contains(upperNeighbor)) {
				bigN.add(upperNeighbor);
			}
		}

		// update lower neigbors info
		for (List<List<Double>> upperNeighbor : latticeOfBonds.get(
				multiFunction).get('u')) {
			if (latticeOfBonds.get(upperNeighbor) == null) {
				latticeOfBonds.put(upperNeighbor,
						new HashMap<Character, Set<List<List<Double>>>>());
			}

			if (latticeOfBonds.get(upperNeighbor).get('l') == null) {
				latticeOfBonds.get(upperNeighbor).put('l',
						new HashSet<List<List<Double>>>());
			}

			latticeOfBonds.get(upperNeighbor).get('l').add(multiFunction);
		}

		for (List<List<Double>> newlyFoundNeighbor : bigN) {
			boolean isFullFuzzySet = true;

			for (List<Double> fuzzySet : newlyFoundNeighbor) {
				if (notFullFuzzySet(fuzzySet)) {
					isFullFuzzySet = false;
					break;
				}
			}

			if (!isFullFuzzySet) {
				generateFrom(newlyFoundNeighbor);
			} else {
				// take care of upper neighbors of full fuzzy set - largest
				// element in the lattice, it has no upper neighbors
				latticeOfBonds.get(newlyFoundNeighbor).put('u',
						new HashSet<List<List<Double>>>());
			}
		}
	}

	/**
	 * Returns all upper neighbours of input bond after finitely many steps
	 * 
	 * @param multiFunction
	 *            bond
	 * @return set of all upperneigbors
	 */
	private Set<List<List<Double>>> upperNeighbors(
			List<List<Double>> multiFunction) {
		Set<List<List<Double>>> upperNeighbors = new HashSet<List<List<Double>>>();

		Set<Integer> min1 = new HashSet<Integer>();
		Set<Integer> min2 = new HashSet<Integer>();

		for (int b1 = 0; b1 < multiFunction.size(); b1++) {
			if (notFullFuzzySet(multiFunction.get(b1))) {
				min1.add(b1);
			}
		}

		List<List<Double>> transposedMultiFunction = transpose(multiFunction);
		for (int a2 = 0; a2 < transposedMultiFunction.size(); a2++) {
			if (notFullFuzzySet(transposedMultiFunction.get(a2))) {
				min2.add(a2);
			}
		}

		for (int b1 = 0; b1 < multiFunction.size(); b1++) {
			if (notFullFuzzySet(multiFunction.get(b1))) {
				List<List<List<Double>>> setOfGammas = moveB(multiFunction, b1);

				for (List<List<Double>> gamma : setOfGammas) {
					Set<Integer> increased = new HashSet<Integer>();
					for (int i = 0; i < c1.getObjectsCount(); i++) {
						if (b1 != i
								&& !multiFunction.get(i).equals(gamma.get(i))) {
							// TODO subset
							increased.add(i);
						}
//						if (b1 != i && isSubset(multiFunction.get(i), gamma.get(i))) {
//							increased.add(i);
//						}
					}

					boolean isIntersectionEmpty = true;					
					for (int i : increased) {
						if (min1.contains(i)) {
							isIntersectionEmpty = false;
							break;
						}
					}

					if (isIntersectionEmpty) {
						upperNeighbors.add(gamma);
					} else {
						min1.remove(b1);
					}
				}

			}
		}

		for (int a2 = 0; a2 < transposedMultiFunction.size(); a2++) {
			if (notFullFuzzySet(transposedMultiFunction.get(a2))) {
				List<List<List<Double>>> setOfDeltas = moveA(
						transposedMultiFunction, a2);

				for (List<List<Double>> delta : setOfDeltas) {
					Set<Integer> increased = new HashSet<Integer>();
					for (int i = 0; i < c2.getAttributesCount(); i++) {
						if (a2 != i
								&& !transposedMultiFunction.get(i).equals(
										delta.get(i))) {
							//							 TODO subset
							increased.add(i);
						}
//						if (a2 != i && isSubset(transposedMultiFunction.get(i), delta.get(i))) {
//							increased.add(i);
//						}
					}

					boolean isIntersectionEmpty = true;

					for (int i : increased) {
						if (min2.contains(i)) {
							isIntersectionEmpty = false;
							break;
						}
					}
					
					if (isIntersectionEmpty) {
						upperNeighbors.add(transpose(delta));
					} else {
						min2.remove(a2);
					}
				}

			}
		}

		return upperNeighbors;
	}

	/**
	 * Returns true, if given fuzzy set has all values equal to 1.0
	 * 
	 * @param fuzzySet
	 * @return true or false
	 */
	private boolean notFullFuzzySet(List<Double> fuzzySet) {
		for (int i = 0; i < fuzzySet.size(); i++) {
			if (fuzzySet.get(i) != 1.0) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Generates all possible upper neighbours of input bond delta by
	 * substituting of extent from first context as a truth value of input
	 * attribute by its upper neighbour in the complete lattice of all extents
	 * of first context.
	 * 
	 * @param delta
	 *            bond
	 * @param attributeIndex
	 *            the index where extent will be replaced
	 * @return list of all possible upper neighbors
	 */
	private List<List<List<Double>>> moveA(List<List<Double>> delta,
			int attributeIndex) {
		// delta - multifunction
		List<List<List<Double>>> setOfDeltas = new ArrayList<List<List<Double>>>();

		List<Double> extent = delta.get(attributeIndex);

		List<List<Double>> upperNeighbors = c1.getUpperNeighbors().get(extent);

		for (List<Double> upperNeighbor : upperNeighbors) {
			List<List<Double>> newDelta = replaceFuzzySetInMultiFunction(delta,
					upperNeighbor, attributeIndex);
			
			List<List<Double>> rightClosedNewDelta = rightMultiFunctionClosure(newDelta);

			boolean isBond = isBond(c2, transpose(rightClosedNewDelta),
					ClosureType.DOWN_UP);
			if (isBond) {
				setOfDeltas.add(rightClosedNewDelta);
			}
		}

		return setOfDeltas;
	}

	/**
	 * Generates all possible upper neighbours of input bond gamma by
	 * substituting of intent from second context as a truth value of input
	 * object by its upper neighbour in the complete lattice of all intents of
	 * second context.
	 * 
	 * @param gamma
	 *            bond
	 * @param objectIndex
	 *            the index where intent will be replaced
	 * @return list of all possible upper neighbors
	 */
	private List<List<List<Double>>> moveB(List<List<Double>> gamma,
			int objectIndex) {
		// gamma - multifunction
		List<List<List<Double>>> setOfGammas = new ArrayList<List<List<Double>>>();

		List<Double> intent = gamma.get(objectIndex);

		List<List<Double>> upperNeighbors = c2.getUpperNeighbors().get(intent);

		for (List<Double> upperNeighbor : upperNeighbors) {
			List<List<Double>> newGamma = replaceFuzzySetInMultiFunction(gamma,
					upperNeighbor, objectIndex);
			
			List<List<Double>> leftClosedNewGamma = leftMultiFunctionClosure(newGamma);

			boolean isBond = isBond(c1, transpose(leftClosedNewGamma),
					ClosureType.UP_DOWN);
			if (isBond) {
				setOfGammas.add(leftClosedNewGamma);
			}
		}

		return setOfGammas;
	}

	/**
	 * Replaces given fuzzy set in given multifunction
	 * 
	 * @param multiFunction
	 *            multifunction
	 * @param fuzzySet
	 *            fuzzy set
	 * @param index
	 *            where fuzzy set is about to replace
	 * @return new multifunction
	 */
	private List<List<Double>> replaceFuzzySetInMultiFunction(
			List<List<Double>> multiFunction, List<Double> fuzzySet, int index) {
		List<List<Double>> newMultiFunction = new ArrayList<List<Double>>(
				multiFunction);
		newMultiFunction.set(index, fuzzySet);
		return newMultiFunction;
	}

	/**
	 * Makes right multifunction closure of given multifunction. <br />
	 * 1. transpose the multifunction <br />
	 * 2. close every fuzzy set of the multifunction in second context <br />
	 * 3. transpose the multifunction <br />
	 * 4. close every fuzzy set of the multifunction in first context
	 * 
	 * @param multiFunction
	 * @return closed multifunction
	 */
	private List<List<Double>> rightMultiFunctionClosure(
			List<List<Double>> multiFunction) {
		List<List<Double>> rightClosedMultiFunction = new ArrayList<List<Double>>();

		rightClosedMultiFunction = transpose(multiFunction);

		rightClosedMultiFunction = multiFunctionClosure(
				rightClosedMultiFunction, ClosureType.DOWN_UP);

		rightClosedMultiFunction = transpose(rightClosedMultiFunction);

		rightClosedMultiFunction = multiFunctionClosure(
				rightClosedMultiFunction, ClosureType.UP_DOWN);

		return rightClosedMultiFunction;
	}

	/**
	 * Makes left multifunction closure of given multifunction. <br />
	 * 1. transpose the multifunction <br />
	 * 2. close every fuzzy set of the multifunction in first context <br />
	 * 3. transpose the multifunction <br />
	 * 4. close every fuzzy set of the multifunction in second context
	 * 
	 * @param multiFunction
	 * @return closed multifunction
	 */
	private List<List<Double>> leftMultiFunctionClosure(
			List<List<Double>> multiFunction) {
		List<List<Double>> leftClosedMultiFunction = new ArrayList<List<Double>>();

		leftClosedMultiFunction = transpose(multiFunction);

		leftClosedMultiFunction = multiFunctionClosure(leftClosedMultiFunction,
				ClosureType.UP_DOWN);

		leftClosedMultiFunction = transpose(leftClosedMultiFunction);

		leftClosedMultiFunction = multiFunctionClosure(leftClosedMultiFunction,
				ClosureType.DOWN_UP);

		return leftClosedMultiFunction;
	}

	/**
	 * Auxiliary function which acts either as left closure or as right closure
	 * 
	 * @param multiFunction
	 * @param closureType
	 *            type of closure
	 * @return closed multifunction
	 */
	private List<List<Double>> multiFunctionClosure(
			List<List<Double>> multiFunction, ClosureType closureType) {
		List<List<Double>> closedMultiFunction = new ArrayList<List<Double>>();

		if (closureType == ClosureType.DOWN_UP) {
			for (int i = 0; i < c1.getObjectsCount(); i++) {
				List<Double> intent = multiFunction.get(i);
				intent = c2.closure(intent, closureType);
				closedMultiFunction.add(i, intent);
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			for (int i = 0; i < c2.getAttributesCount(); i++) {
				List<Double> extent = multiFunction.get(i);
				extent = c1.closure(extent, closureType);
				closedMultiFunction.add(i, extent);
			}
		}
		return closedMultiFunction;
	}

	/**
	 * Makes transposition of given multifunction
	 * 
	 * @param multifunction
	 * @return transposed multifunction
	 */
	private List<List<Double>> transpose(List<List<Double>> multifunction) {
		List<List<Double>> result = new ArrayList<List<Double>>();

		for (int i = 0; i < multifunction.get(0).size(); i++) {
			List<Double> temp = new ArrayList<Double>();
			for (int j = 0; j < multifunction.size(); j++) {
				temp.add(multifunction.get(j).get(i));
			}
			result.add(temp);
		}

		return result;
	}

	/**
	 * Decides whether given multifunction is bond or not
	 * 
	 * @param c
	 *            context
	 * @param multiFunction
	 * @param closureType
	 *            type of closure
	 * @return true or false
	 */
	private boolean isBond(Context c, List<List<Double>> multiFunction,
			ClosureType closureType) {

		if (multiFunction == null || multiFunction.isEmpty()) {
			throw new RuntimeException(
					"Please provide multifunction which is not empty");
		}

		if (closureType == ClosureType.DOWN_UP) {

			if (c.getIntents().isEmpty()) {
				throw new RuntimeException(
						"There are no intents found. Please run method lattice(ClosureType.DOWN_UP) which take care of it!");
			}

			if (multiFunction.get(0).size() != c.getIntents().get(0).size()) {
				throw new RuntimeException(
						String.format(
								"It is impossible to compare fuzzy set from multifunction with size %d and intent with size %d. Try to change closure type!",
								multiFunction.get(0).size(), c.getIntents()
										.get(0).size()));
			}

			for (List<Double> fuzzySet : multiFunction) {
				if (!c.getIntents().contains(fuzzySet)) {
					return false;
				}
			}
		} else if (closureType == ClosureType.UP_DOWN) {

			if (c.getExtents().isEmpty()) {
				throw new RuntimeException(
						"There are no extents found. Please run method lattice(ClosureType.UP_DOWN) which take care of it!");
			}

			if (multiFunction.get(0).size() != c.getExtents().get(0).size()) {
				throw new RuntimeException(
						String.format(
								"It is impossible to compare fuzzy set from multifunction with size %d and extent with size %d. Try to change closure type!",
								multiFunction.get(0).size(), c.getExtents()
										.get(0).size()));
			}

			for (List<Double> fuzzySet : multiFunction) {
				if (!c.getExtents().contains(fuzzySet)) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Makes direct product of given context
	 * 
	 * @param context1
	 * @param context2
	 * @return direct product context
	 */
	public Context directProduct(Context context1, Context context2) {
		Context directProduct = null;
		Integer logicDegree1 = context1.getLogic().getLogicDegree();
		Integer logicDegree2 = context2.getLogic().getLogicDegree();

		if (logicDegree1 != logicDegree2) {
			System.err
					.println("It is not possible to do direct product of contexts with different logic degree");
		}

		Logic logic = new Logic(logicDegree1);

		Double[][] relation1 = context1.getRelation();
		Double[][] relation2 = context2.getRelation();

		Double[][] directProductRelation = new Double[relation1.length
				* relation2[0].length][relation1[0].length * relation2.length];

		int p = 0;
		int q = 0;
		for (int i = 0; i < relation1.length; i++) {
			for (int j = 0; j < relation2[0].length; j++) {
				for (int k = 0; k < relation2.length; k++) {
					for (int l = 0; l < relation1[0].length; l++) {
						directProductRelation[p][q] = logic.disjunction(
								relation1[i][l], relation2[k][j]);
						q++;
					}
				}
				p++;
				q = 0;
			}
		}

		directProduct = new Context(logicDegree1, directProductRelation);

		return directProduct;
	}

	/**
	 * This method makes use of knowledge about direct products between given
	 * contexts, so that every extent of direct product is bond automatically.
	 * Firstly it builds direct product of input contexts, after that discovers
	 * lattice of concepts and verifies whether the extent of every concept is
	 * bond or not
	 * 
	 * @param context1
	 * @param context2
	 * @return set of bonds between input contexts
	 */
	public Set<List<List<Double>>> findBondsUsingDirectProduct(
			Context context1, Context context2) {
		Set<List<List<Double>>> result = new HashSet<List<List<Double>>>();

		Context directProduct = directProduct(context1, context2);
		directProduct.lattice(ClosureType.UP_DOWN);

		Set<Concept> concepts = directProduct.getConcepts();

		int objectsCount = context1.getObjectsCount();
		int attributesCount = context2.getAttributesCount();

		for (Concept concept : concepts) {
			List<Double> extent = concept.getExtent();
			List<List<Double>> multiFunction = splitFuzzySetToMultifunction(
					extent, objectsCount, attributesCount);

			// check if it is bond
			if (isBond(c2, multiFunction, ClosureType.DOWN_UP)) {
				result.add(multiFunction);
			}
		}

		return result;
	}

	/**
	 * Returns multifunction which has values from given fuzzy set. Sizes of new
	 * multifunction has to be provided as parameters.
	 * 
	 * @param fuzzySet
	 * @param objectsCount
	 * @param attributesCount
	 * @return new multifunction
	 */
	private List<List<Double>> splitFuzzySetToMultifunction(
			List<Double> fuzzySet, int objectsCount, int attributesCount) {
		List<List<Double>> multiFunction = new ArrayList<List<Double>>();

		for (int i = 0; i < objectsCount; i++) {
			List<Double> row = fuzzySet.subList(i * attributesCount, (i + 1)
					* attributesCount);
			multiFunction.add(new ArrayList<Double>(row));
		}

		return multiFunction;
	}

	// ! first approach how to get naturally small context using knowledge about
	// exactly one lower or upper neighbor !
	/**
	 * Returns context which is naturally small because its lattice of concepts
	 * is isomorphic to the lattice of all bonds between given contexts
	 * 
	 * @return context
	 */
	public List<List<Double>> getNaturallySmallContext() {
		List<List<Double>> naturallySmallContext = new ArrayList<List<Double>>();

		List<List<List<Double>>> supIrreducibles = getIrreducibles(IrreducibleType.SUPREMUM);
		List<List<List<Double>>> infIrreducibles = getIrreducibles(IrreducibleType.INFIMUM);

		// build naturally small context using the set of supremum irreducibles
		// and infimum irreducibles
		// place supremum irreducibles to rows of the context and infimum
		// irreducibles to columns of the context
		for (int j = 0; j < supIrreducibles.size(); j++) {
			naturallySmallContext.add(new ArrayList<Double>());
			for (int k = 0; k < infIrreducibles.size(); k++) {
				naturallySmallContext.get(j).add(0.0);
			}
		}

		// set TRUE, if particular supremum irreducible element is lower than
		// particular infimum irreducible element
		for (int j = 0; j < supIrreducibles.size(); j++) {
			for (int k = 0; k < infIrreducibles.size(); k++) {

				if (isLowerNeighbor(supIrreducibles.get(j),
						infIrreducibles.get(k))) {
					naturallySmallContext.get(j).set(k, 1.0);
				}
			}
		}

		return naturallySmallContext;
	}

	/**
	 * Returns all irreducible elements, either supremum or infimum ones
	 * 
	 * @param type
	 *            irreducible type
	 * @return list of all irreducible elements
	 */
	private List<List<List<Double>>> getIrreducibles(IrreducibleType type) {
		List<List<List<Double>>> irreducibles = new ArrayList<List<List<Double>>>();

		switch (type) {
		case SUPREMUM:
			for (List<List<Double>> bond : latticeOfBonds.keySet()) {
				// find bonds with 1 lower neighbor in order to find infimum
				// irreducible elements of the lattice of bonds
				if (latticeOfBonds.get(bond).get('l').size() == 1) {
					irreducibles.add(bond);
				}
			}
			break;
		case INFIMUM:
			for (List<List<Double>> bond : latticeOfBonds.keySet()) {
				// find bond with 1 upper neighbor in order to find supremum
				// irreducible elements of the lattice of bonds
				if (latticeOfBonds.get(bond).get('u').size() == 1) {
					irreducibles.add(bond);
				}
			}
			break;
		}

		return irreducibles;
	}

	/**
	 * Decides whether first given multifunction is lower neighbor of second
	 * given multifunction
	 * 
	 * @param multiFunction1
	 * @param multiFunction2
	 * @return true or false
	 */
	private boolean isLowerNeighbor(List<List<Double>> multiFunction1,
			List<List<Double>> multiFunction2) {

		// compare 2 same-sized multifunctions whether one is lower than another
		// one

		if (multiFunction1.size() != multiFunction2.size()) {
			throw new MultifunctionComparisonException(
					"You are not allowed to compare multifunctions with different number of objects");
		}

		if (multiFunction1.get(0).size() != multiFunction2.get(0).size()) {
			throw new MultifunctionComparisonException(
					"You are not allowed to compare multifunctions with different number of attributes");
		}

		for (int i = 0; i < multiFunction1.size(); i++) {
			for (int j = 0; j < multiFunction1.get(0).size(); j++) {
				double d1 = multiFunction1.get(i).get(j);
				double d2 = multiFunction2.get(i).get(j);
				if (d1 > d2) {
					return false;
				}
			}
		}
		return true;
	}
	
	private boolean isSubset(List<Double> fuzzySet1, List<Double> fuzzySet2) {
		for (int i = 0; i < fuzzySet1.size(); i++) {
			double d1 = fuzzySet1.get(i);
			double d2 = fuzzySet2.get(i);
			if (d1 > d2) {
				return false;
			}
		}
		return true;
	}
	

	// ! second approach how to get naturally small context using infimum and
	// supremum of bonds !
	// /**
	// * Returns context which is naturally small because its lattice of
	// concepts
	// * is isomorphic to the lattice of all bonds between given contexts
	// *
	// * @return context
	// */
	// public List<List<Double>> getNaturallySmallContext() {
	// List<List<Double>> naturallySmallContext = new ArrayList<List<Double>>();
	//
	// List<List<List<Double>>> supIrreducibles =
	// getIrreducibles(IrreducibleType.SUPREMUM);
	// List<List<List<Double>>> infIrreducibles =
	// getIrreducibles(IrreducibleType.INFIMUM);
	//
	// // build naturally small context using the set of supremum irreducibles
	// // and infimum irreducibles
	// // place supremum irreducibles to rows of the context and infimum
	// // irreducibles to columns of the context
	//
	// for (int j = 0; j < supIrreducibles.size(); j++) {
	// naturallySmallContext.add(new ArrayList<Double>());
	// for (int k = 0; k < infIrreducibles.size(); k++) {
	// naturallySmallContext.get(j).add(0.0);
	// }
	// }
	//
	// // set TRUE, if particular supremum irreducible element is lower than
	// // particular infimum irreducible element
	//
	// for (int j = 0; j < supIrreducibles.size(); j++) {
	// for (int k = 0; k < infIrreducibles.size(); k++) {
	//
	// if (isLowerNeighbor(supIrreducibles.get(j),
	// infIrreducibles.get(k))) {
	// naturallySmallContext.get(j).set(k, 1.0);
	// }
	// }
	// }
	//
	// return naturallySmallContext;
	// }
	//
	// /**
	// * Returns all irreducible elements, either supremum or infimum ones
	// *
	// * @param type
	// * irreducible type
	// * @return list of all irreducible elements
	// */
	// private List<List<List<Double>>> getIrreducibles(IrreducibleType type) {
	// List<List<List<Double>>> irreducibles = new
	// ArrayList<List<List<Double>>>();
	//
	// switch (type) {
	// case SUPREMUM:
	//
	// for (List<List<Double>> bond : latticeOfBonds.keySet()) {
	// Set<List<List<Double>>> lowerNeighbors = latticeOfBonds.get(
	// bond).get('l');
	// List<List<Double>> supremumOfBonds = supremumOfBonds(lowerNeighbors);
	// List<List<Double>> closure = leftMultiFunctionClosure(supremumOfBonds);
	// boolean equal = isEqual(bond, closure);
	// if (!equal) {
	// irreducibles.add(bond);
	// }
	// }
	// break;
	// case INFIMUM:
	//
	// for (List<List<Double>> bond : latticeOfBonds.keySet()) {
	// Set<List<List<Double>>> upperNeighbors = latticeOfBonds.get(
	// bond).get('u');
	// List<List<Double>> infimumOfBonds = infimumOfBonds(upperNeighbors);
	// boolean equal = isEqual(bond, infimumOfBonds);
	// if (!equal) {
	// irreducibles.add(bond);
	// }
	// }
	// break;
	// }
	//
	// return irreducibles;
	// }
	//
	// /**
	// * Decides whether first given multifunction is lower neighbor of second
	// * given multifunction
	// *
	// * @param multiFunction1
	// * @param multiFunction2
	// * @return true or false
	// */
	// private boolean isLowerNeighbor(List<List<Double>> multiFunction1,
	// List<List<Double>> multiFunction2) {
	//
	// // compare 2 same-sized multifunctions whether one is lower than another
	// // one
	//
	// if (multiFunction1.size() != multiFunction2.size()) {
	// throw new MultifunctionComparisonException(
	// "You are not allowed to compare multifunctions with different number of objects");
	// }
	//
	// if (multiFunction1.get(0).size() != multiFunction2.get(0).size()) {
	// throw new MultifunctionComparisonException(
	// "You are not allowed to compare multifunctions with different number of attributes");
	// }
	//
	// for (int i = 0; i < multiFunction1.size(); i++) {
	// for (int j = 0; j < multiFunction1.get(0).size(); j++) {
	// double d1 = multiFunction1.get(i).get(j);
	// double d2 = multiFunction2.get(i).get(j);
	// if (d1 > d2) {
	// return false;
	// }
	// }
	// }
	// return true;
	// }
	//
	// /**
	// * Decides whether first given multifunction is equal to second given
	// * multifunction
	// *
	// * @param multiFunction1
	// * @param multiFunction2
	// * @return true or false
	// */
	// private boolean isEqual(List<List<Double>> multiFunction1,
	// List<List<Double>> multiFunction2) {
	//
	// if (multiFunction1.size() != multiFunction2.size()) {
	// throw new MultifunctionComparisonException(
	// "You are not allowed to compare multifunctions with different number of objects");
	// }
	//
	// if (multiFunction1.get(0).size() != multiFunction2.get(0).size()) {
	// throw new MultifunctionComparisonException(
	// "You are not allowed to compare multifunctions with different number of attributes");
	// }
	//
	// for (int i = 0; i < multiFunction1.size(); i++) {
	// for (int j = 0; j < multiFunction1.get(0).size(); j++) {
	// double d1 = multiFunction1.get(i).get(j);
	// double d2 = multiFunction2.get(i).get(j);
	// if (d1 != d2) {
	// return false;
	// }
	// }
	// }
	// return true;
	// }
	//
	// /**
	// * Return supremum of given set of bonds
	// * @param setOfBonds
	// * @return supremum of bonds
	// */
	// private List<List<Double>> supremumOfBonds(
	// Set<List<List<Double>>> setOfBonds) {
	//
	// List<List<Double>> result = new ArrayList<List<Double>>();
	// for (int i = 0; i < c1.getObjectsCount(); i++) {
	// result.add(new ArrayList<Double>());
	// for (int j = 0; j < c2.getAttributesCount(); j++) {
	// result.get(i).add(0.0);
	// }
	// }
	//
	// for (int i = 0; i < c1.getObjectsCount(); i++) {
	// for (int j = 0; j < c2.getAttributesCount(); j++) {
	// for (List<List<Double>> bond : setOfBonds) {
	// double d1 = result.get(i).get(j);
	// double d2 = bond.get(i).get(j);
	// result.get(i).set(j, Math.max(d1, d2));
	// }
	// }
	// }
	// return result;
	// }
	//
	// /**
	// * Returns infimum of given set of bonds
	// * @param setOfBonds
	// * @return infimum of bonds
	// */
	// private List<List<Double>> infimumOfBonds(Set<List<List<Double>>>
	// setOfBonds) {
	//
	// List<List<Double>> result = new ArrayList<List<Double>>();
	// for (int i = 0; i < c1.getObjectsCount(); i++) {
	// result.add(new ArrayList<Double>());
	// for (int j = 0; j < c2.getAttributesCount(); j++) {
	// result.get(i).add(1.0);
	// }
	// }
	//
	// for (int i = 0; i < c1.getObjectsCount(); i++) {
	// for (int j = 0; j < c2.getAttributesCount(); j++) {
	// for (List<List<Double>> bond : setOfBonds) {
	// double d1 = result.get(i).get(j);
	// double d2 = bond.get(i).get(j);
	// result.get(i).set(j, Math.min(d1, d2));
	// }
	// }
	// }
	// return result;
	// }
}